import Promise from "Promise.js";

var READY_STATE = "loaded|complete",
	TEST_INTERACTIVE = !!this.ActiveXObject, // effectively, IE
	document = this.document,
	scripts = {},
	uid = 0,
	timeout = 10000,
	queue = [],
	head;

// Injects a script tag into the document
export function loadScript(src, data) {

	var s = document.createElement("script"),
	    promise = new Promise(),
		k = "@" + (uid++),
		value;
	
	value = {
	
		element: s, 
		src: src, 
		data: data
	};
	
	scripts[k] = { promise, value };
	
	s._key = k;
	s.type = "text/javascript";
	s.src = src;
	s.async = true;
	
	s.onload = 
	s.onerror = 
	s.onreadystatechange = () => {
	
	    if (typeof s.readyState === "undefined" ||
			READY_STATE.indexOf(s.readyState) >= 0) {
		
			completed(s);
		}
	};
	
	toHead(s);
	startTimer(s);
	setContext(scripts[k]);
	
	return promise.future;
}

// Starts a timeout timer for a script
function startTimer(s) {

	poll();
	
	function poll() {
	
		// Opera reports "interactive" while script is fetching.  Poll
		// in order to determine if HTTP error is encountered
		if (s.readyState === "interactive") {
		
			s._timer = setTimeout(poll, 200);
		
		} else if (!s._timer) {
		
			s._timer = setTimeout(poll, timeout);
		
		} else {
		
			s._timer = 0;
			completed(s);
		}
	}
}

// Called when script tag has finished executing
function completed(s) {

	if (s._done) 
		return;
	
	var key = s._key,
		obj = scripts[key],
		val = obj.value;
	
	// Clear timeout
	if (s._timer) 
		clearTimeout(s._timer);
	
	// Clear memory in IE
	if (s.clearAttributes) 
		try { s.clearAttributes(); } 
		catch (x) {}
	
	// Cleanup
	delete scripts[key];
	s._done = true;
	
	setContext(obj);
	obj.promise.resolve(val);
}

// Executes a callback when the calling script src is known
export function getContext(fn) {

	var key, val;
	
	queue.push(fn);

	// For IE, find executing script by looking for "interactive" state
	if (TEST_INTERACTIVE) {
	
		for (key in scripts) {
		
			val = scripts[key].value;
			
			if (val.element.readyState === "interactive") {
			
				setContext(scripts[key]);
				break;
			}
		}
	}
}

// Sets the current script context
function setContext(script) {

	var fn;
	
	// Call all queued context callbacks
	while (fn = queue.shift())
		fn.call(script.value.element, script.value);
}

// Inserts an element in the head of the document
function toHead(elem) {

	var d = elem.ownerDocument,
		h = (head = d.getElementsByTagName("head")[0]) || 
			d.getElementsByTagName("body")[0] ||
			d.documentElement.firstChild;
	
	h.insertBefore(elem, h.firstChild);
}
